﻿using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using SkiaSharp;
using System.Security.Cryptography;
using System.Text;
using PmSoft.Cache.Abstractions;
using PmSoft.Core;

namespace PmSoft.Web.Abstractions.Captcha;

/// <summary>
/// 点选文字验证码类
/// </summary>
public class ClickCaptchaService
{
	/// <summary>
	/// 验证码配置选项
	/// </summary>
	private readonly ClickCaptchaOptions Options;

	/// <summary>
	/// 可用的背景图片路径数组
	/// </summary>
	private readonly string[] BgPaths;

	/// <summary>
	/// 可用的字体文件路径数组
	/// </summary>
	private readonly string[] FontPaths;

	/// <summary>
	/// 图标目录
	/// </summary>
	private readonly string IconDir;

	/// <summary>
	/// 验证点图标名称到中文描述的映射表
	/// </summary>
	private readonly Dictionary<string, string> IconDict;

	/// <summary>
	/// 分布式缓存实例，用于存储验证码数据
	/// </summary>
	private readonly IDistributedCache Cache;

	/// <summary>
	/// 验证码数据的内部类
	/// </summary>
	private class CaptchaData
	{
		public string Key { get; set; }          // 验证码键
		public string Code { get; set; }         // 验证码校验码
		public string CaptchaJson { get; set; }  // 验证码 JSON 数据
		public DateTime CreateTime { get; set; } // 创建时间
		public TimeSpan ExpireTime { get; set; } // 过期时间
	}

	/// <summary>
	/// 表示验证码 JSON 数据的结构
	/// </summary>
	private class CaptchaJsonData
	{
		public int Width { get; set; }           // 图片宽度
		public int Height { get; set; }          // 图片高度
		public List<TextItem> Text { get; set; } // 验证点列表
	}

	/// <summary>
	/// 表示单个验证点的数据结构
	/// </summary>
	private class TextItem
	{
		public bool Icon { get; set; }           // 是否为图标
		public string? Name { get; set; }        // 图标名称（如果是图标）
		public string Text { get; set; }         // 显示的文本
		public int Size { get; set; }            // 字体或图标大小
		public int Width { get; set; }           // 宽度
		public int Height { get; set; }          // 高度
		public int X { get; set; }               // X 坐标
		public int Y { get; set; }               // Y 坐标
	}

	/// <summary>
	/// 构造函数，初始化验证码实例
	/// </summary>
	/// <param name="cache">分布式缓存实例</param>
	/// <param name="configuration">配置实例，用于加载 click-captcha.json</param>
	public ClickCaptchaService(IDistributedCache cache, IConfiguration configuration)
	{
		Cache = cache ?? throw new ArgumentNullException(nameof(cache));
		if (configuration == null) throw new ArgumentNullException(nameof(configuration));

		// 加载配置和图标映射表
		Options = configuration.GetSection("ClickCaptchaConfig").Get<ClickCaptchaOptions>() ?? new ClickCaptchaOptions();
		IconDict = configuration.GetSection("ClickCaptchaConfig:IconDict").Get<Dictionary<string, string>>()
			?? throw new InvalidOperationException("未找到 IconDict 配置");

		// 初始化背景图片和字体路径
		var captchaDir = Path.Combine(Environment.CurrentDirectory, "Config", "Captcha");
		string backgroundDir = Options.BackgroundDir;
		if (string.IsNullOrEmpty(backgroundDir))
			backgroundDir = Path.Combine(captchaDir, "click", "bgs");
		var fontDir = Options.FontDir;
		if (string.IsNullOrEmpty(fontDir))
			fontDir = Path.Combine(captchaDir, "fonts", "zhttfs");
		IconDir = string.IsNullOrEmpty(Options.IconDir) ? Path.Combine(captchaDir, "click", "icons") : Options.IconDir;

		BgPaths = Directory.GetFiles(backgroundDir, "*.png").Select(Path.GetFullPath).ToArray();
		FontPaths = Directory.GetFiles(fontDir, "*.ttf").Select(Path.GetFullPath).ToArray();

		// 验证字体文件是否有效
		if (FontPaths.Length == 0)
			throw new InvalidOperationException("未找到任何 TTF 字体文件，请检查 FontDir 配置");
	}

	/// <summary>
	/// 创建图形验证码
	/// </summary>
	/// <param name="id">开发者自定义的验证码 ID</param>
	/// <returns>包含验证码图片 base64 编码和文字信息的字典</returns>
	public async Task<Dictionary<string, object>> CreateAsync(string id)
	{
		var rand = new Random();
		string imagePath = BgPaths[rand.Next(BgPaths.Length)]; // 随机选择背景图片
		string fontPath = FontPaths[rand.Next(FontPaths.Length)]; // 随机选择字体
		var randPoints = RandPoints(Options.Length + Options.ConfuseLength); // 生成随机验证点

		var textItems = new List<TextItem>();
		using var typeface = SKTypeface.FromFile(fontPath); // 加载支持中文的字体
		if (typeface == null)
			throw new InvalidOperationException($"无法加载字体文件: {fontPath}");

		foreach (var point in randPoints)
		{
			var tmp = new TextItem { Size = rand.Next(15, 31) };
			if (IconDict.ContainsKey(point))
			{
				tmp.Icon = true;
				tmp.Name = point;
				tmp.Text = Thread.CurrentThread.CurrentCulture.Name == "zh-CN" ? $"<{IconDict[point]}>" : $"<{point}>";
				var iconPath = Path.Combine(IconDir, $"{point}.png");
				using var iconImg = SKBitmap.Decode(File.ReadAllBytes(iconPath)); // 使用 Decode 加载图标
				if (iconImg == null)
					throw new InvalidOperationException($"无法加载图标文件: {iconPath}");
				tmp.Width = iconImg.Width;
				tmp.Height = iconImg.Height;
			}
			else
			{
				tmp.Icon = false;
				tmp.Text = point;
				//using var paint = new SKPaint {  };
				using var font = new SKFont { Typeface = typeface, Size = tmp.Size };
				var width = font.MeasureText(point); // 使用 MeasureText 计算宽度
				var fontMetrics = font.Metrics;
				tmp.Width = (int)width;
				tmp.Height = (int)(fontMetrics.Descent - fontMetrics.Ascent); // 使用字体度量计算高度
			}
			textItems.Add(tmp);
		}

		using var bgImage = SKBitmap.Decode(File.ReadAllBytes(imagePath)); // 加载背景图片
		if (bgImage == null)
			throw new InvalidOperationException($"无法加载背景图片: {imagePath}");

		var captchaJsonData = new CaptchaJsonData
		{
			Width = bgImage.Width,
			Height = bgImage.Height,
			Text = textItems
		};

		// 为每个验证点分配随机位置
		foreach (var v in captchaJsonData.Text)
		{
			var pos = RandPosition(captchaJsonData.Text, bgImage.Width, bgImage.Height, v.Width, v.Height, v.Icon);
			v.X = pos[0];
			v.Y = pos[1];
		}

		using var canvas = new SKCanvas(bgImage); // 创建画布
		foreach (var v in captchaJsonData.Text)
		{
			if (v.Icon)
			{
				using var iconImg = SKBitmap.Decode(File.ReadAllBytes(Path.Combine(IconDir, $"{v.Name}.png")));
				using var paint = new SKPaint { Color = SKColors.White.WithAlpha((byte)(Options.Alpha * 255 / 100)) }; // 设置图标透明度
				canvas.DrawBitmap(iconImg, new SKPoint(v.X, v.Y), paint);
			}
			else
			{
				using var font = new SKFont { Typeface = typeface, Size = v.Size };
				using var paint = new SKPaint
				{
					Color = SKColors.White.WithAlpha((byte)(255 - (Options.Alpha * 255 / 100))), // 设置文本透明度
					IsAntialias = true // 抗锯齿
				};
				// 使用推荐的 DrawText 重载
				canvas.DrawText(v.Text, new SKPoint(v.X, v.Y + v.Height), font, paint);
			}
		}

		var now = DateTime.UtcNow;
		var textList = captchaJsonData.Text.Take(Options.Length).Select(t => t.Text).ToList();
		var captchaData = new CaptchaData
		{
			Key = ComputeMd5(id),
			Code = ComputeMd5(string.Join(",", textList)),
			CaptchaJson = Json.Stringify(captchaJsonData), // 使用对象序列化
			CreateTime = now,
			ExpireTime = TimeSpan.FromSeconds(Options.Expire)
		};

		// 异步存储到分布式缓存
		await Cache.SetAsync(captchaData.Key, captchaData, captchaData.ExpireTime);

		using var ms = new MemoryStream();
		bgImage.Encode(ms, SKEncodedImageFormat.Png, 100); // 编码为 PNG
		return new Dictionary<string, object>
		{
			{ "id", id },
			{ "text", textList },
			{ "base64", $"data:image/png;base64,{Convert.ToBase64String(ms.ToArray())}" },
			{ "width", captchaJsonData.Width },
			{ "height", captchaJsonData.Height }
		};
	}

	/// <summary>
	/// 检查验证码是否正确
	/// </summary>
	/// <param name="id">验证码 ID</param>
	/// <param name="info">验证信息（格式如 "x1,y1-x2,y2;w;h"）</param>
	/// <param name="unset">验证成功后是否删除验证码</param>
	/// <returns>验证成功返回 true，否则返回 false</returns>
	public async Task<bool> CheckAsync(string id, string info, bool unset = true)
	{
		string key = ComputeMd5(id);

		var captcha = await Cache.GetAsync<CaptchaData>(key);
		if (captcha == null)
			return false;

		if (DateTime.UtcNow > captcha.CreateTime.Add(captcha.ExpireTime))
		{
			Cache.Remove(key); // 移除过期验证码
			return false;
		}

		var captchaJsonData = Json.Parse<CaptchaJsonData>(captcha.CaptchaJson);
		if (captchaJsonData == null)
			throw new InvalidOperationException("无法解析验证码 JSON 数据");

		var parts = info.Split(';');
		var xyArr = parts[0].Split('-');
		double w = double.Parse(parts[1]), h = double.Parse(parts[2]);
		double xPro = w / captchaJsonData.Width; // 宽度比例
		double yPro = h / captchaJsonData.Height; // 高度比例

		var textList = captchaJsonData.Text;
		for (int k = 0; k < xyArr.Length; k++)
		{
			var xy = xyArr[k].Split(',');
			double x = double.Parse(xy[0]), y = double.Parse(xy[1]);
			var item = textList[k];
			if (x / xPro < item.X || x / xPro > item.X + item.Width)
				return false;

			double phStart = item.Icon ? item.Y : item.Y;
			double phEnd = item.Icon ? item.Y + item.Height : item.Y + item.Height;
			if (y / yPro < phStart || y / yPro > phEnd)
				return false;
		}

		if (unset) Cache.Remove(key); // 移除验证成功的验证码
		return true;
	}

	/// <summary>
	/// 随机生成验证点元素（文字或图标）
	/// </summary>
	/// <param name="length">生成的数量</param>
	/// <returns>随机验证点列表</returns>
	private List<string> RandPoints(int length)
	{
		var arr = new List<string>();
		var rand = new Random();
		if (Options.Mode.Contains("text"))
		{
			string zhSet = Options.ZhSet;
			for (int i = 0; i < length; i++)
				arr.Add(zhSet[rand.Next(zhSet.Length)].ToString());
		}
		if (Options.Mode.Contains("icon"))
		{
			var icons = IconDict.Keys.OrderBy(x => rand.Next()).Take(length).ToList();
			arr.AddRange(icons);
		}
		return arr.OrderBy(x => rand.Next()).Take(length).ToList();
	}

	/// <summary>
	/// 随机生成验证点的位置
	/// </summary>
	/// <param name="textArr">已有的验证点数据</param>
	/// <param name="imgW">图片宽度</param>
	/// <param name="imgH">图片高度</param>
	/// <param name="fontW">验证点宽度</param>
	/// <param name="fontH">验证点高度</param>
	/// <param name="isIcon">是否是图标</param>
	/// <returns>随机位置 [x, y]</returns>
	private int[] RandPosition(List<TextItem> textArr, int imgW, int imgH, int fontW, int fontH, bool isIcon)
	{
		var rand = new Random();
		int x = rand.Next(0, imgW - fontW);
		int y = rand.Next(fontH, imgH - fontH);
		return CheckPosition(textArr, x, y, fontW, fontH, isIcon) ? new[] { x, y } : RandPosition(textArr, imgW, imgH, fontW, fontH, isIcon);
	}

	/// <summary>
	/// 检查位置是否发生碰撞
	/// </summary>
	/// <param name="textArr">已有的验证点数据</param>
	/// <param name="x">x 坐标</param>
	/// <param name="y">y 坐标</param>
	/// <param name="w">宽度</param>
	/// <param name="h">高度</param>
	/// <param name="isIcon">是否是图标</param>
	/// <returns>无碰撞返回 true，否则返回 false</returns>
	private bool CheckPosition(List<TextItem> textArr, int x, int y, int w, int h, bool isIcon)
	{
		foreach (var v in textArr)
		{
			bool flagX = x + w < v.X || x > v.X + v.Width;
			double currentPhStart = isIcon ? y : y;
			double currentPhEnd = isIcon ? y + h : y + h;
			double historyPhStart = v.Icon ? v.Y : v.Y;
			double historyPhEnd = v.Icon ? v.Y + v.Height : v.Y + v.Height;
			bool flagY = currentPhEnd < historyPhStart || currentPhStart > historyPhEnd;
			if (!flagX && !flagY) return false;
		}
		return true;
	}

	/// <summary>
	/// 计算字符串的 MD5 哈希值
	/// </summary>
	/// <param name="input">输入字符串</param>
	/// <returns>MD5 哈希值（小写）</returns>
	private string ComputeMd5(string input)
	{
		using var md5 = MD5.Create();
		var bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
		return BitConverter.ToString(bytes).Replace("-", "").ToLower();
	}
}

